﻿using System;

namespace CommonFramework.FixMath
{
    [Serializable]
    public struct LVector2Int : IEquatable<LVector2Int>
    {
        public static readonly LVector2Int Zero = new LVector2Int(0, 0);
        public static readonly LVector2Int One = new LVector2Int(1, 1);
        public static readonly LVector2Int Up = new LVector2Int(0, 1);
        public static readonly LVector2Int Down = new LVector2Int(0, -1);
        public static readonly LVector2Int Left = new LVector2Int(-1, 0);
        public static readonly LVector2Int Right = new LVector2Int(1, 0);

        private int _x;
        private int _y;

        public LVector2Int(int x, int y)
        {
            this._x = x;
            this._y = y;
        }

        /// <summary>
        ///   <para>X component of the vector.</para>
        /// </summary>
        public int X
        {
            get { return this._x; }
            set { this._x = value; }
        }

        /// <summary>
        ///   <para>Y component of the vector.</para>
        /// </summary>
        public int Y
        {
            get { return this._y; }
            set { this._y = value; }
        }

        /// <summary>
        ///   <para>Set x and y components of an existing Vector2Int.</para>
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public void Set(int x, int y)
        {
            this._x = x;
            this._y = y;
        }

        public int this[int index]
        {
            get
            {
                if (index == 0)
                    return this.X;
                if (index == 1)
                    return this.Y;
                throw new IndexOutOfRangeException(string.Format("Invalid Vector2Int index addressed: {0}!",
                    (object)index));
            }
            set
            {
                if (index != 0)
                {
                    if (index != 1)
                        throw new IndexOutOfRangeException(string.Format("Invalid Vector2Int index addressed: {0}!",
                            (object)index));
                    this.Y = value;
                }
                else
                    this.X = value;
            }
        }

        /// <summary>
        ///   <para>Returns the length of this vector (Read Only).</para>
        /// </summary>
        public FFloat Magnitude
        {
            get { return FMathUtil.Sqrt(FFloat.FromLong(this.X * this.X + this.Y * this.Y)); }
        }

        /// <summary>
        ///   <para>Returns the squared length of this vector (Read Only).</para>
        /// </summary>
        public int SqrMagnitude
        {
            get { return this.X * this.X + this.Y * this.Y; }
        }

        /// <summary>
        ///   <para>Returns the distance between a and b.</para>
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        public static FFloat Distance(LVector2Int a, LVector2Int b)
        {
            var num1 = (a.X - b.X);
            var num2 = (a.Y - b.Y);
            return FMathUtil.Sqrt(FFloat.FromRawLong((long)num1 * num1 + num2 * num2));
        }

        /// <summary>
        ///   <para>Returns a vector that is made from the smallest components of two vectors.</para>
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        public static LVector2Int Min(LVector2Int lhs, LVector2Int rhs)
        {
            return new LVector2Int(FMathUtil.Min(lhs.X, rhs.X), FMathUtil.Min(lhs.Y, rhs.Y));
        }

        /// <summary>
        ///   <para>Returns a vector that is made from the largest components of two vectors.</para>
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        public static LVector2Int Max(LVector2Int lhs, LVector2Int rhs)
        {
            return new LVector2Int(FMathUtil.Max(lhs.X, rhs.X), FMathUtil.Max(lhs.Y, rhs.Y));
        }

        /// <summary>
        ///   <para>Multiplies two vectors component-wise.</para>
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        public static LVector2Int Scale(LVector2Int a, LVector2Int b)
        {
            return new LVector2Int(a.X * b.X, a.Y * b.Y);
        }

        /// <summary>
        ///   <para>Multiplies every component of this vector by the same component of scale.</para>
        /// </summary>
        /// <param name="scale"></param>
        public void Scale(LVector2Int scale)
        {
            this.X *= scale.X;
            this.Y *= scale.Y;
        }

        /// <summary>
        ///   <para>Clamps the Vector2Int to the bounds given by min and max.</para>
        /// </summary>
        /// <param name="min"></param>
        /// <param name="max"></param>
        public void Clamp(LVector2Int min, LVector2Int max)
        {
            this.X = FMathUtil.Max(min.X, this.X);
            this.X = FMathUtil.Min(max.X, this.X);
            this.Y = FMathUtil.Max(min.Y, this.Y);
            this.Y = FMathUtil.Min(max.Y, this.Y);
        }


        public static explicit operator FVector3Int(LVector2Int v)
        {
            return new FVector3Int(v.X, v.Y, 0);
        }


        public static LVector2Int operator +(LVector2Int a, LVector2Int b)
        {
            return new LVector2Int(a.X + b.X, a.Y + b.Y);
        }

        public static LVector2Int operator -(LVector2Int a, LVector2Int b)
        {
            return new LVector2Int(a.X - b.X, a.Y - b.Y);
        }

        public static LVector2Int operator *(LVector2Int a, LVector2Int b)
        {
            return new LVector2Int(a.X * b.X, a.Y * b.Y);
        }

        public static LVector2Int operator *(LVector2Int a, int b)
        {
            return new LVector2Int(a.X * b, a.Y * b);
        }

        public static bool operator ==(LVector2Int lhs, LVector2Int rhs)
        {
            return lhs.X == rhs.X && lhs.Y == rhs.Y;
        }

        public static bool operator !=(LVector2Int lhs, LVector2Int rhs)
        {
            return !(lhs == rhs);
        }

        /// <summary>
        ///   <para>Returns true if the objects are equal.</para>
        /// </summary>
        /// <param name="other"></param>
        public override bool Equals(object other)
        {
            if (!(other is LVector2Int))
                return false;
            return this.Equals((LVector2Int)other);
        }

        public bool Equals(LVector2Int other)
        {
            return this.X.Equals(other.X) && this.Y.Equals(other.Y);
        }

        /// <summary>
        ///   <para>Gets the hash code for the Vector2Int.</para>
        /// </summary>
        /// <returns>
        ///   <para>The hash code of the Vector2Int.</para>
        /// </returns>
        public override int GetHashCode()
        {
            return this.X.GetHashCode() ^ this.Y.GetHashCode() << 2;
        }

        /// <summary>
        ///   <para>Returns a nicely formatted string for this vector.</para>
        /// </summary>
        public override string ToString()
        {
            return string.Format("({0}, {1})", (object)this.X, (object)this.Y);
        }
    }
}